/**
 *  This file is part of GlToolkit project https://code.google.com/p/gltoolkit/
 *
 *  Copyright (C) 2010 Synble Ltd.
 *  All rights reserved.
 *  
 *  This library is provided 'as-is', without any express or implied
 *  warranty. In no event will the authors be held liable for any damages
 *  arising from the use of this software.
 *  
 *  Permission to use and alter this library requires a valid agreement
 *  with Synble Ltd and GetLocalization.com.
 *  
 *  You can use (including commercial applications) and alter this 
 *  library freely under the GetLocalization.com terms of service.
 *  Altered parts should be clearly marked as such.  
 *  
 */

#include <s32file.h>
#include <HttpStringConstants.h>
#include <http\RHTTPTransaction.h>
#include <http\RHTTPSession.h>
#include <http\RHTTPHeaders.h>
#include <commdbconnpref.h>
#include <aknnotewrappers.h>
#include <AknListQueryDialog.h>
#include <aknwaitdialog.h>
#include <avkon.rsg>
#include <utf.h>
#include <eikmenup.h>
#include <aknsettingitemlist.h>
#include <S32MEM.H>
#include "LocEnv.h"
#include "log.h"

#include <GoogaSync_0x20023FCD.rsg>

CLocEnv::CLocEnv() :
	CCoeStatic(KEnvUid, CCoeStatic::EApp)
	{
	// No implementation required
	}

CLocEnv::~CLocEnv()
	{
	iCache.ResetAndDestroy();
	}

EXPORT_C CLocEnv* CLocEnv::Static()
	{
	CLocEnv* self = static_cast<CLocEnv*> (CCoeEnv::Static()->FindStatic(
			KEnvUid));

	if (!self)
		{
		CLocEnv* self = new (ELeave) CLocEnv();
		TRAPD(err, self->ConstructL());
		__ASSERT_ALWAYS(!err,Panic(KEnvPanicInitFailed));
		}

	return self;
	}

void CLocEnv::ConstructL()
	{
	}

void CLocEnv::GetLanguageFile(TDes& aPathAndName)
	{
	_LIT(KFileName, "gl.loc");

	TBuf<255> privatePath;
	if (CEikonEnv::Static()->FsSession().PrivatePath(privatePath) == KErrNone)
		{
		TParse p;

#ifdef __WINSCW__
		TPtrC processName = _L("C:\\");
#else
		TPtrC processName = RProcess().FileName();
#endif

		p.Set(privatePath, &processName, NULL );
		aPathAndName.Copy(p.DriveAndPath());
		aPathAndName.Append(KFileName);
		}
	}

void CLocEnv::InitLanguageL()
	{
	RFs fs = CCoeEnv::Static()->FsSession();
	RFileReadStream locFile;

	TBuf<255> langFile;
	GetLanguageFile(langFile);

	User::LeaveIfError(locFile.Open(fs, langFile, EFileRead));
	CleanupClosePushL(locFile);

	TInt err = KErrNone;
	while (err == KErrNone)
		{
		TRAP(err,
					{
					// Read original string, i.e. the source of translation.
					RBuf8 origString;
					CleanupClosePushL(origString);
					TInt32 origStrLen = locFile.ReadInt32L();
					origString.CreateMax(origStrLen+1);
					locFile.ReadL(origString, origStrLen);

					// Convert utf8 string to unicode so we can calculate
					// hash. 
					HBufC* os = CnvUtfConverter::ConvertToUnicodeFromUtf8L(origString);
					CleanupStack::PushL(os);
					TUint32 checkSum = 0;
					checkSum = HashPJW(os->Des());
					CleanupStack::PopAndDestroy(2); // origString, os

					// Read the translation
					TInt32 strLen = locFile.ReadInt32L();
					RBuf8 string;
					CleanupClosePushL(string);
					string.CreateMax(strLen);
					locFile.ReadL(string, strLen);

					// Cache the hash and translation.
					HBufC* ts = CnvUtfConverter::ConvertToUnicodeFromUtf8L(string);
					iCache.Append(new(ELeave)CLocString(checkSum,ts));
					CleanupStack::PopAndDestroy(); // string
					});
		}

	// Sort everything based on checksums so it's faster to find them. 
	TLinearOrder<CLocString> order(CLocString::Compare);
	iCache.Sort(order);

	CleanupStack::PopAndDestroy(); // locFile
	}

void CLocEnv::DynInitMenuPaneL(TInt /*aResourceId*/, CEikMenuPane* aMenuPane)
	{
	if (iCache.Count() > 0)
		{
		for (TInt i = 0; i < aMenuPane->NumberOfItemsInPane(); i++)
			{
			CEikMenuPaneItem::SData& item = aMenuPane->ItemDataByIndexL(i);
			aMenuPane->SetItemTextL(item.iCommandId, LS(item.iText));
			}
		}
	}

void CLocEnv::ConstructSettingItemListL(CAknSettingItemList* aList, TInt aResId)
	{
	TResourceReader reader;
	CCoeEnv::Static()->CreateResourceReaderLC(reader, aResId);

	CBufFlat* resData = CBufFlat::NewL(1024);
	CleanupStack::PushL(resData);

	RBufWriteStream ws(*resData);
	CleanupClosePushL(ws);

	TInt16 flags = reader.ReadInt16();
	ws.WriteInt16L(flags);

	TPtrC settingListTitle = LS(reader.ReadTPtrC());
	ws.WriteInt8L(settingListTitle.Length());
	ws.WriteInt8L(0xAB);
	ws.WriteL(settingListTitle);

	TInt16 initialOrdinal = reader.ReadInt16();
	ws.WriteInt16L(initialOrdinal);

	TInt16 numberOfItems = reader.ReadInt16();
	ws.WriteInt16L(numberOfItems);
	TInt id;

	for (TInt ii = 0; ii < numberOfItems; ++ii)
		{
		id = reader.ReadInt16();
		ws.WriteInt16L(id);

		TPtrC settingName = LS(reader.ReadTPtrC());
		ws.WriteInt8L(settingName.Length());
		if (settingName != KNullDesC)
			{
			ws.WriteInt8L(0xAB);
			ws.WriteL(settingName);
			}

		TInt settingPageResourceId = reader.ReadInt32();
		ws.WriteInt32L(settingPageResourceId);

		TInt editorControlType = reader.ReadInt16();
		ws.WriteInt16L(editorControlType);

		TInt settingEditorResourceId = reader.ReadInt32();
		ws.WriteInt32L(settingEditorResourceId);

		TInt associatedResourceId = reader.ReadInt32();
		ws.WriteInt32L(associatedResourceId);

		TPtrC emptyText = LS(reader.ReadTPtrC());
		ws.WriteInt8L(emptyText.Length());
		if (emptyText != KNullDesC)
			{
			ws.WriteInt8L(0xAB);
			ws.WriteL(emptyText);
			}

		TPtrC compulsoryText = LS(reader.ReadTPtrC());
		ws.WriteInt8L(compulsoryText.Length());
		if (compulsoryText != KNullDesC)
			{
			ws.WriteInt8L(0xAB);
			ws.WriteL(compulsoryText);
			}

		TInt reserved = reader.ReadInt32();
		ws.WriteInt32L(reserved);
		}

	HBufC8* buff = resData->Ptr(0).AllocLC();
	TResourceReader reader2;
	reader2.SetBuffer(buff);
	aList->ConstructFromResourceL(reader2);
	CleanupStack::PopAndDestroy(3); // resData, wc, buff
	}

void CLocEnv::TranslateEnumeratedTextPopupSettingItem(
		CAknEnumeratedTextPopupSettingItem* item)
	{
	CArrayPtr<CAknEnumeratedText>* enumTexts = item->EnumeratedTextArray();

	for (TInt i = 0; i < enumTexts->Count(); i++)
		{
		CAknEnumeratedText* text = (*enumTexts)[i];
		HBufC* orig = text->Value();
		HBufC* ls = LS(*orig).AllocL();
		delete orig;
		text->SetValue(ls);
		}
	item->LoadL();
	item->UpdateListBoxTextL();
	}

TUint32 CLocEnv::HashPJW(const TDesC& aString)
	{
	/* Peter Weinberger's */
	TUint32 h = 0, g = 0;

	for (TInt i = 0; i < aString.Length(); i++)
		{
		h = (h << 4) + aString[i];
		if ((g = h & 0xF0000000))
			{
			h ^= g >> 24;
			h ^= g;
			}
		}

	return h;
	}

TPtrC CLocEnv::GetString(const TDesC& aString)
	{
	// Calculate checksum for the string. 
	TUint32 checkSum = 0;
	checkSum = HashPJW(aString);

	// Look the translation for this string from our cache.
	TInt index =
			iCache.FindInOrder<TUint32> (checkSum, CLocString::FastCompare);

	if (index >= 0)
		return iCache[index]->iData->Des();
	else
		return aString;
	}

TBool CLocString::IsEqual(const CLocString& aLeft, const CLocString& aRight)
	{
	return aLeft.iCheckSum == aRight.iCheckSum;
	}

TInt CLocString::Compare(const CLocString& aLeft, const CLocString& aRight)
	{
	return aLeft.iCheckSum - aRight.iCheckSum;
	}

TInt CLocString::FastCompare(const TUint32* aCheckSum, const CLocString& aItem)
	{
	return (*aCheckSum) - aItem.iCheckSum;
	}

/*****************************************************************************
 * CGLClientUI
 * 
 * Simple dialog based UI to select & download selected language. 
 *****************************************************************************/
CGLClientUI* CGLClientUI::NewL()
	{
	DBG;
	CGLClientUI* self = new (ELeave) CGLClientUI();
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop();
	return self;
	}

CGLClientUI::CGLClientUI()
	{
	}

void CGLClientUI::ConstructL()
	{
	DBG;
	iClient = CGLClient::NewL(*this);
	}

CGLClientUI::~CGLClientUI()
	{
	delete iClient;
	}

void CGLClientUI::ChangeLanguageL()
	{
	DBG;
	iClient->StartConnectionL();
	iWaitDialog = new (ELeave) CAknWaitDialog(
			(REINTERPRET_CAST(CEikDialog**,&iWaitDialog)), ETrue);
	iWaitDialog->SetCallback(this);

	iWaitDialog->ExecuteLD(R_WAIT_NOTE);
	iWaitDialog->SetTextL(_LL("Connecting..."));
	}

void CGLClientUI::GLClientEvent(TInt aEventType)
	{
	DBG;
	if (aEventType == EGLConnectionUp)
		{
		iClient->GetLanguagesL();
		}
	else if (aEventType == EGLGetLanguagesCompleted)
		{
		if (iWaitDialog)
			{
			iWaitDialog->ProcessFinishedL();
			iWaitDialog = NULL;
			}

		TInt selectedLang;
		CAknListQueryDialog* dialog = new (ELeave) CAknListQueryDialog(
				&selectedLang);

		CDesCArray* languages = new (ELeave) CDesCArrayFlat(1);
		CleanupStack::PushL(languages);
		CDesC8Array* languageCodes = new (ELeave) CDesC8ArrayFlat(1);
		CleanupStack::PushL(languageCodes);

		languages->AppendL(_LL("Original language"));
		languageCodes->AppendL(_L8("None"));

		ParseLanguageList(iClient->BufferPtrL(), *languages, *languageCodes);

		dialog->PrepareLC(R_LANGUAGE_QUERY);
		dialog->SetItemTextArray(languages);
		dialog->SetOwnershipType(ELbmDoesNotOwnItemArray);
		dialog->SetHeaderTextL(_LL("Select language"));

		if (dialog->RunLD() == EAknSoftkeyOk)
			{
			if (selectedLang == 0)
				{
				RFs fs = CCoeEnv::Static()->FsSession();
				TBuf<255> langFile;
				CLocEnv::Static()->GetLanguageFile(langFile);
				fs.Delete(langFile); // Delete existing translation, omit the error.

				CAknInformationNote* note = new (ELeave) CAknInformationNote(
						ETrue);
				note->ExecuteLD(
						_LL("Language was successfully installed. Application needs to be restarted."));
				}
			else
				{
				iClient->GetTranslationL((*languageCodes)[selectedLang]);
				iWaitDialog = new (ELeave) CAknWaitDialog(
						(REINTERPRET_CAST(CEikDialog**,&iWaitDialog)), ETrue);
				iWaitDialog->SetCallback(this);
				iWaitDialog->ExecuteLD(R_WAIT_NOTE);
				iWaitDialog->SetTextL(_LL("Downloading..."));
				}
			}

		CleanupStack::PopAndDestroy(); // languages
		}
	if (aEventType == EGLGetTranslationCompleted)
		{
		if (iWaitDialog)
			{
			iWaitDialog->ProcessFinishedL();
			iWaitDialog = NULL;
			}
		RFs fs = CCoeEnv::Static()->FsSession();
		RFileWriteStream locFile;

		TBuf<255> langFile;
		CLocEnv::Static()->GetLanguageFile(langFile);
		TInt err = fs.Delete(langFile); // Delete existing translation, omit the error.

		if (locFile.Create(fs, langFile, EFileWrite) == KErrNone)
			{
			CleanupClosePushL(locFile);
			locFile.WriteL(iClient->BufferPtrL()->Des());
			locFile.CommitL();

			CAknInformationNote* note = new (ELeave) CAknInformationNote(ETrue);
			note->ExecuteLD(_LL("Language was successfully installed. Application needs to be restarted."));
			CleanupStack::PopAndDestroy();
			}
		else
			{
			CAknInformationNote* note = new (ELeave) CAknInformationNote(ETrue);
			note->ExecuteLD(_LL("Language installation failed."));
			}
		}
	if (aEventType == EGLServerError)
		{
		if (iWaitDialog)
			{
			iWaitDialog->ProcessFinishedL();
			iWaitDialog = NULL;
			}

		#ifdef _DEBUG
		RFs fs = CCoeEnv::Static()->FsSession();
		RFileWriteStream locFile;
		_LIT(KFileName, "error.html");
		locFile.Create(fs, KFileName, EFileWrite);
		CleanupClosePushL(locFile);
		locFile.WriteL(iClient->BufferPtrL()->Des());
		locFile.CommitL();
		CleanupStack::PopAndDestroy();
		#endif
		
		}
	}

void CGLClientUI::DialogDismissedL(TInt aButtonId)
	{
	DBG;
	LOG_INT(aButtonId);
	if (aButtonId == -1)
		{
		iClient->CancelGet();
		}
	}
void CGLClientUI::ParseLanguageList(HBufC8* aData, CDesCArray& aLanguages,
		CDesC8Array& aLanguageCodes)
	{
	TPtrC8 data = aData->Des();
	TInt si = 0;

	for (TInt i = 0; i < data.Length(); i++)
		{
		switch (data[i])
			{
			case '\t':
				{
				HBufC* lang = CnvUtfConverter::ConvertToUnicodeFromUtf8L(
						data.Mid(si, i - si));
				CleanupStack::PushL(lang);
				aLanguages.AppendL(lang->Des());
				CleanupStack::PopAndDestroy(); //lang
				si = i + 1;
				break;
				}
			case '\r':
				{
				TPtrC8 languageCode = data.Mid(si, i - si);
				aLanguageCodes.AppendL(languageCode);
				break;
				}
			case '\n':
				{
				si = i + 1;
				break;
				}
			}
		}
	}

/*****************************************************************************
 * CGLClient
 * 
 * Downloads languages & localization strings from Get Localization servers.
 * 
 *****************************************************************************/
CGLClient* CGLClient::NewL(MGLClientObserver& aObserver)
	{
	DBG;
	CGLClient* self = new (ELeave) CGLClient(aObserver);
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop();
	return self;
	}

CGLClient::CGLClient(MGLClientObserver& aObserver) :
	CActive(EPriorityStandard), iObserver(aObserver), iIapID(KErrNotFound)
	{
	}

void CGLClient::ConstructL()
	{
	DBG;
	CActiveScheduler::Add(this);
	User::LeaveIfError(iSocketServ.Connect());
	User::LeaveIfError(iConnection.Open(iSocketServ));
	iSession.OpenL();
	RHTTPConnectionInfo connInfo = iSession.ConnectionInfo();
	RStringPool stringPool = iSession.StringPool();
	connInfo.SetPropertyL(stringPool.StringF(HTTP::EHttpSocketServ,
			RHTTPSession::GetTable()), THTTPHdrVal(iSocketServ.Handle()));
	connInfo.SetPropertyL(stringPool.StringF(HTTP::EHttpSocketConnection,
			RHTTPSession::GetTable()), THTTPHdrVal(
			reinterpret_cast<TInt> (&iConnection)));
	}

CGLClient::~CGLClient()
	{
	DBG;
	iSession.Close();
	iConnection.Close();
	iSocketServ.Close();
	delete iBuffer;
	}

/**
 *	Set access point. KErrNotFound means that it is asked from the user. 
 */
void CGLClient::SetAccessPoint(TInt aIapID)
	{
	DBG;
	iIapID = aIapID;
	}

/**
 *	Start connection. 
 */
void CGLClient::StartConnectionL()
	{
	DBG;
	iCancelled = EFalse;
	if (!iConnUp)
		{
		if (iIapID != KErrNotFound)
			{
			TCommDbConnPref pref;
			pref.SetIapId(iIapID);
			pref.SetDialogPreference(ECommDbDialogPrefDoNotPrompt);
			iConnection.Start(pref, iStatus);
			SetActive();
			}
		else
			{
			iConnection.Start(iStatus);
			SetActive();
			}
		}
	else
		{
		iObserver.GLClientEvent(EGLConnectionUp);
		}
	}

void CGLClient::GetL(const TDesC8& aUrl)
	{
	DBG;
	if (!iConnUp)
		{
		User::LeaveIfError(iConnection.Start());
		iConnUp = ETrue;
		}

	if (iBuffer)
		{
		delete iBuffer;
		iBuffer = NULL;
		}

	iBuffer = HBufC8::NewL(1);
	RStringF method = iSession.StringPool().StringF(HTTP::EGET,
			RHTTPSession::GetTable());
	TUriParser8 parser;
	parser.Parse(aUrl);
	iTransaction = iSession.OpenTransactionL(parser, *this, method);

	RHTTPHeaders hdr = iTransaction.Request().GetHeaderCollection();
	SetHeaderL(hdr, HTTP::EUserAgent, KUserAgent);

	iTransaction.SubmitL();
	iActiveTransaction = ETrue;
	}

/**
 *	Start a download.
 */
void CGLClient::GetLanguagesL()
	{
	DBG;
	GetL(
			_L8("http://www.getlocalization.com/api/languages/?type=text&product=GoogaSync"));
	iObserver.GLClientEvent(EGLConnecting);
	iState = EGLGetLanguages;
	}

/**
 *	Start a download.
 */
void CGLClient::GetTranslationL(const TDesC8& aLanguageCode)
	{
	DBG;
	_LIT8(KUrl, "http://www.getlocalization.com/api/translations/?type=bin&product=GoogaSync&iana=%S");
	RBuf8 url;
	url.Create(KUrl().Length() + aLanguageCode.Length());
	url.AppendFormat(KUrl, &aLanguageCode);
	CleanupClosePushL(url);
	GetL(url);
	CleanupStack::PopAndDestroy(); // url

	iObserver.GLClientEvent(EGLGetTranslation);
	iState = EGLGetTranslation;
	}

HBufC8* CGLClient::BufferPtrL()
	{
	DBG;
	return iBuffer;
	}

void CGLClient::CancelGet()
	{
	iCancelled = ETrue;
	if (iActiveTransaction)
		{
		iTransaction.Cancel();
		iTransaction.Close();
		}
	}

void CGLClient::SetHeaderL(RHTTPHeaders aHeaders, TInt aHdrField,
		const TDesC8& aHdrValue)
	{
	DBG;
	RStringF valStr = iSession.StringPool().OpenFStringL(aHdrValue);
	CleanupClosePushL(valStr);
	THTTPHdrVal val(valStr);
	aHeaders.SetFieldL(iSession.StringPool().StringF(aHdrField,
			RHTTPSession::GetTable()), val);
	CleanupStack::PopAndDestroy();
	}

void CGLClient::SetHeaderL(RHTTPHeaders aHeaders, const TDesC8& aKey,
		const TDesC8& aHdrValue)
	{
	DBG;
	RStringF keyStr = iSession.StringPool().OpenFStringL(aKey);
	CleanupClosePushL(keyStr);

	RStringF valStr = iSession.StringPool().OpenFStringL(aHdrValue);
	CleanupClosePushL(valStr);

	THTTPHdrVal val(valStr);
	aHeaders.SetFieldL(keyStr, val);

	CleanupStack::PopAndDestroy(2);
	}

void CGLClient::MHFRunL(RHTTPTransaction aTransaction, const THTTPEvent& aEvent)
	{
	DBG;
	switch (aEvent.iStatus)
		{
		case THTTPEvent::EGotResponseHeaders:
			{
			//RHTTPResponse resp = aTransaction.Response();
			//TInt status = resp.StatusCode();
			break;
			}
		case THTTPEvent::EGotResponseBodyData:
			{
			MHTTPDataSupplier* body = aTransaction.Response().Body();

			TPtrC8 dataChunk;
			body->GetNextDataPart(dataChunk);

			iBuffer = iBuffer->ReAlloc(iBuffer->Size() + dataChunk.Length());

			if (iBuffer)
				{
				iBuffer->Des().Append(dataChunk);
				}
			else
				{
				iObserver.GLClientEvent(KErrNoMemory);
				aTransaction.Close();
				}

			body->ReleaseData();

			break;
			}
		case THTTPEvent::EResponseComplete:
			{
			break;
			}

		case THTTPEvent::ESucceeded:
			{
			if (iState == EGLGetLanguages)
				{
				iObserver.GLClientEvent(EGLGetLanguagesCompleted);
				}
			else if (iState == EGLGetTranslation)
				{
				iObserver.GLClientEvent(EGLGetTranslationCompleted);
				}

			aTransaction.Close();
			iActiveTransaction = EFalse;
			}
			break;

		case THTTPEvent::EFailed:
			{
			iObserver.GLClientEvent(EGLServerError);
			aTransaction.Close();
			iActiveTransaction = EFalse;
			}
			break;

		default:
			{
			iObserver.GLClientEvent(aEvent.iStatus);
			aTransaction.Close();
			iActiveTransaction = EFalse;
			iObserver.GLClientEvent(aEvent.iStatus);
			break;
			}
		}
	}

TInt CGLClient::MHFRunError(TInt /*aError*/, RHTTPTransaction /*aTransaction*/,
		const THTTPEvent& /*aEvent*/)
	{
	DBG;
	return 0;
	}

void CGLClient::RunL()
	{
	DBG;
	if (iCancelled)
		{
		iConnUp = EFalse;
		}
	else if (iStatus.Int() == KErrNone || iStatus.Int() == KErrAlreadyExists)
		{
		iConnUp = ETrue;
		iObserver.GLClientEvent(EGLConnectionUp);
		}
	else
		{
		iObserver.GLClientEvent(EGLConnectionError);
		}
	}

void CGLClient::DoCancel()
	{
	DBG;
	CancelGet();
	}

